import sys
import logging
import string
from itertools import chain

from formencode import Schema, Invalid

from clutch.globals import C
from clutch.view import engines
from clutch.exceptions import TemplateNotFound

__all__ = [ 'Tope', 'CompoundTope', 'Field' ]

log = logging.getLogger(__name__)

class DictMaker(object):
    def to_python(self, value, state=None):
        if not value: return None
        else: return value
    def from_python(self, value, state=None):
        if not value: return {}
        else: return value

class Tope(object):
    engine = None
    template = None
    validator = None
    attrs = {}
    js_requires = []
    css_requires = []
    layout_only = False
    wrapper_args = dict()
    
    _dojo_requires = []
    _dojo_onload = []

    def __init__(self, name, **kw):
        cls = self.__class__
        self.name = name
        self._label = None
        for k,v in kw.items():
            setattr(self, k, v)
        self.engine = engines.get(self.engine, self.template)
        try:
            loader = self.engine.loader(cls.__module__, 'templates')
            self.template = loader.template(self.template)
        except TemplateNotFound, exc:
            t = sys.exc_info()
            try:
                self.template = self.engine.template_from_string(self.template)
            except:
                raise t[0], t[1], t[2]
            del t

    def sa_columns(self):
        '''Return iterator of AppColumn objects to be used in a
        SQLAlchemy schema'''
        return []

    def validate(self, value):
        if self.validator:
            return self.validator.to_python(value)
        else:
            return value

    def display(self, value=None, **kw):
        # @@@ TODO: Make display work for re-display of error values
        if self.validator:
            try:
                value = self.validator.from_python(value)
            except Invalid:
                log.warning('Validation (from python) failed in %s', self.__class__)
                pass
        ns = dict(
            tope=self,
            name=self.name,
            value=value,
            attrs=self.attrs)
        ns.update(kw)
        return self.template.generate(ns)

    def render(self, value=None, **kw):
        body = str(self.display(value, **kw))
        l = ['<script type="text/javascript">']
        for req in C.dojo_requires():
            l.append('dojo.require("%s")' % req)
        l.append('</script>')
        l.append(body)
        return '\n'.join(l)

    def dojo_requires(self):
        return self._dojo_requires

    def dojo_onload(self, name=None):
        if name is None: name = self.name
        ns = dict(
            tope=self,
            name=name,
            attrs=self.attrs)
        for tpl in self._dojo_onload:
            code = string.Template(tpl)
            # code = self.engine.template_from_string(tpl)
            yield code.safe_substitute(ns)

    def __repr__(self):
        return '<%s %s>' % (self.__class__.__name__, self.name)

class Field(Tope):
    engine='genshi'
    template='basic_field.html'
    label=None
    help=None
    show_label=True

    def __init__(self, inner, **kw):
        self.inner = inner
        Tope.__init__(self, inner.name, **kw)
        if self.label is None:
            self.label = getattr(inner, 'label', self.name.capitalize())
        if self.help is None:
            self.help = getattr(inner, 'help', None)
        self.validator = inner.validator
        for k,v in inner.wrapper_args.items():
            setattr(self, k, v)
            
    def dojo_requires(self):
        return self._dojo_requires + self.inner.dojo_requires()

    def dojo_onload(self, name=None):
        if name is None: name = self.name
        return chain(Tope.dojo_onload(self, name), self.inner.dojo_onload(name))

    def display(self, value=None, **kw):
        return Tope.display(self, value=value, kwargs=kw, **kw)

    def __repr__(self):
        return '%s: %s' % (self.label, self.inner)

class CompoundTope(Tope):
    engine='genshi'
    template='default_compound.html'
    field_wrapper = Field
    fields = []

    def __init__(self, name, fields=None, **kw):
        if fields is None: fields = []
        Tope.__init__(self, name, **kw)
        self.fields = []
        for f in fields:
            if f.layout_only:
                self.fields.append(f)
            else:
                self.fields.append(self.field_wrapper(f))
        cls = self.__class__
        # Build validation schema
        subvals = dict()
        for fld in self.fields:
            if fld.validator:
                subvals[fld.name] = fld.validator
        if cls.validator is not None:
            chained_validators = [ cls.validator, DictMaker() ]
        else:
            chained_validators = [ DictMaker() ]
        self.validator = Schema(if_missing={}, chained_validators=chained_validators, **subvals)

    def dojo_requires(self):
        l = list(self._dojo_requires)
        for f in self.fields:
            l += f.dojo_requires()
        return l

    def dojo_onload(self, name=None):
        if name is None: name = self.name
        l = list(self._dojo_onload)
        for f in self.fields:
            l += f.dojo_onload(self.name_for(f, name))
        return l

    def name_for(self, field, name=None):
        if name is None: name = self.name
        if name is None:
            return field.name
        else:
            return name + '.' + field.name
        
    def value_for(self, value, field):
        result = value.get(field.name, {})
        # log.info('value for %s = %s', field, result)
        return result

    def error_for(self, field, name=None):
        name = self.name_for(field, name)
        if C.validation_errors and name in C.validation_errors:
            return C.validation_errors[name]
        else:
            return None
        
    def __repr__(self):
        fields = '\n'.join(repr(f) for f in self.fields)
        fields = '    ' + fields.replace('\n', '\n    ')
        return '<%s %s>\n%s\n</%s>' % (
            self.__class__.__name__, self.name,
            fields,
            self.__class__.__name__)

